/**
 * Contiene las clases que referencian los objetos
 */
package co.sumset.vista;


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Date;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

import co.sumset.cache.BodegaImagenes;
import co.sumset.cache.SoundCache;
import co.sumset.cache.SpriteCache;
import co.sumset.entidades.Partida;
import co.sumset.objetos.Actor;
import co.sumset.objetos.Agente;
import co.sumset.objetos.Base;
import co.sumset.objetos.Escenario;
import co.sumset.objetos.Nivel;
import co.sumset.objetos.Rojo;
import co.sumset.objetos.Vigilante;
import co.sumset.objetos.Virus;


/**
 * Clase Administrador necesaria para inicializar 
 * la ventana para jugar, siendo la clase que reune
 * todos los elementos de la tematica del juego.
 * 
 * @author Cristian Raul Pineda Rodriguez
 */
public class Administrador extends Canvas implements Escenario, KeyListener {

	private static final long serialVersionUID = 1L;

	private BufferStrategy strategy;
	private long usedTime;
	private boolean peligro;

	private int num_inv=0;

	private SpriteCache spriteCache;
	private SoundCache soundCache;

	private ArrayList actors;
	private Vigilante vigilante;
	private Agente agente;
	private Virus virus;
	private Base base;
	private Rojo rojo;

	private BufferedImage background,backgroundTile;
	private int backgroundY;

	private boolean gameEnded=false;

	private int dificultad;

	private long tiempo;
	private int cont_virus;
	private int titulo;

	private boolean pause=false;

	private Thread hiloTiempo;

	private BodegaImagenes bodegaImagenes;

	private VentanaPausa ventanaPausa;

	private VentanaPrincipal ventanaPrincipal;

	private Nivel nivel;

	private int cont_rojos;

	private int puntaje;

	private JFrame ventana;

	private JPanel panel;

	/**
	 * Metodo Constructor. Inicializa las variables para 
	 * preparar tanto el escenario como los obejtos a participar.
	 */
	public Administrador(VentanaPrincipal ventanaPrincipal) 
	{
		bodegaImagenes = new BodegaImagenes();
		spriteCache = new SpriteCache();
		soundCache = new SoundCache();

		ventana = new JFrame();
		ventanaPausa = new VentanaPausa(this);
		ventana.setVisible(false);
		this.ventanaPrincipal=ventanaPrincipal;


		ventana.setTitle("BODY INVADERS");
		ventana.setIconImage(new ImageIcon(getClass().getClassLoader().getResource("res/juego/inv01.png")).getImage());



		panel = (JPanel)ventana.getContentPane();
		setBounds(0,0,Escenario.WIDTH,Escenario.HEIGHT);
		panel.setPreferredSize(new Dimension(Escenario.WIDTH,Escenario.HEIGHT));
		panel.setLayout(null);
		panel.add(this);
		ventana.setBounds(0,0,Escenario.WIDTH,Escenario.HEIGHT);
		ventana.setVisible(true);
		ventana.addWindowListener( new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		ventana.setResizable(false);
		createBufferStrategy(2);
		strategy = getBufferStrategy();
		requestFocus();
		addKeyListener(this);
		setIgnoreRepaint(true);

		BufferedImage cursor = spriteCache.createCompatible(10,10,Transparency.BITMASK);
		Toolkit t = Toolkit.getDefaultToolkit();
		Cursor c = t.createCustomCursor(cursor,new Point(5,5),"null");
		setCursor(c);


	}

	/**
	 * Metodo que permite determinar un juego perdido.
	 * 
	 * @see co.sumset.objetos.Escenario#gameOver()
	 */
	public void gameOver() {
		gameEnded = true;
		guardarDatosJuego();
	}

	/**
	 * Metodo que permite determinar un juego ganado.
	 * 
	 * @see co.sumset.objetos.Escenario#gameWon()
	 */
	public void gameWon() {
		gameEnded = true;
		guardarDatosJuego();
	}

	/**
	 * Metodo que permite iniciar los objetos Vigilantes.
	 */
	public void initVigilantes() {


		int auxX=0;
		int auxY=0;
		for (int i = 0; i <nivel.getNum_vigilante(); i++){
			vigilante = new Vigilante(this);

			/*
			 * Determinamos la ubicacion en el escenario
			 */
			vigilante.set$varX((int)(Math.random()*(Escenario.WIDTH-32)));
			vigilante.set$varY((int)(Math.random()*(Escenario.HEIGHT-200)));

			/*
			 * Determinamos la velocidad
			 */
			auxX=(int)(Math.random()*3);
			auxY=(int)(Math.random()*3);

			/*
			 * Validamos que la velocidad no sea cero.
			 */
			if (auxX==0||auxY==0) {
				vigilante.setVx(1);
				vigilante.setVy(1);
			}else{
				vigilante.setVx(auxX);
				vigilante.setVy(auxY);
			}

			actors.add(vigilante);
		}
	}

	/**
	 * Metodo que permite inicializar los objetos Virus
	 */
	public void initVirus() {

		for (int i = 0; i < nivel.getNum_virus(); i++){
			virus = new Virus(this);

			/*
			 * Determinamos la ubicacion en el escenario
			 */
			virus.set$varX((int)(Math.random()*(Escenario.WIDTH-32)));
			virus.set$varY((int)(Math.random()*(Escenario.HEIGHT-200)));

			/*
			 * Determinamos la velocidad
			 */
			virus.setVx(1);
			virus.setVy(1);

			actors.add(virus);
		}
	}

	/**
	 * Metodo que permite inicializar el objeto agente
	 */
	public void initAgente() {
		agente = new Agente(this);
		agente.set$varX(Escenario.WIDTH/2);
		agente.set$varY(Escenario.HEIGHT - Escenario.PLAY_HEIGHT);
		actors.add(agente);
	}

	/**
	 * Metodo que permite inicializar los objetos bases
	 */
	public void initBases(){

		for (int i = 0; i < getAdministrador().getNivel().getNum_bases(); i++) {
			base = new Base(this);

			if (i==0) {
				base.set$varX(Escenario.WIDTH/2);
				base.set$varY(Escenario.HEIGHT-270);
			}else if (i==1) {
				base.set$varX(Escenario.WIDTH-(Escenario.WIDTH-10));
				base.set$varY(Escenario.HEIGHT-270);
			}else {
				base.set$varX(Escenario.WIDTH-80);
				base.set$varY(Escenario.HEIGHT-270);
			}
			actors.add(base);
		}
	}

	/**
	 * Metodo que permite inicializar los objetos Rojos
	 */
	public void initRojos() {

		int posX=140;
		for (int i = 0; i < Escenario.NUM_ROJOS; i++) {
			rojo = new Rojo(this);

			rojo.set$varX(posX);
			rojo.set$varY(Escenario.HEIGHT-250);

			posX=posX+100;
			int auxY=(int)(Math.random()*6);

			if (auxY==0) {
				rojo.setVy(1);
			}else {
				rojo.setVy(auxY);
			}


			actors.add(rojo);
		}


	}

	/**
	 * Metodo que permite generar mas Rojos, en el arreglo de actores 
	 * se buscan actores de tio Rojo y si en la array no se encuentra
	 * ningun actor de este tipo se procede a generar nuevos Rojos.
	 */
	public void reproducirRojos()
	{
		cont_rojos=0;
		for (int i = 0; i < actors.size(); i++) {

			Actor a= (Actor) actors.get(i);

			if (a instanceof Rojo) {
				cont_rojos++;
			}

		}


		if (cont_rojos==0) 
		{
			initRojos();
		}
	}


	/**
	 * Metodo que permite contabilizar el numero 
	 * de Virus que se encuentran en el escenario
	 */
	public void contarVirus()
	{
		cont_virus=0;
		for (int i = 0; i < actors.size(); i++) {

			Actor a= (Actor) actors.get(i);

			if (a instanceof Virus) {
				cont_virus++;
			}

		}

		if (cont_virus==0) 
		{
			titulo=1;

			this.getAdministrador().gameWon();

		}
	}


	/**
	 * Metodo que permite inicializar las variables y componentes necesarios
	 * para crear todos los objetos para el juego.
	 * 
	 */
	public void initWorld() {

		actors = new ArrayList();

		initVigilantes();
		initAgente();
		initBases();
		initRojos();
		initVirus();

		soundCache.loopSound("musica.wav");

		backgroundTile = spriteCache.getSprite("/fondos/fondo6.png");
		background = spriteCache.createCompatible(Escenario.WIDTH,Escenario.HEIGHT+backgroundTile.getHeight(),Transparency.BITMASK);
		Graphics2D g = (Graphics2D)background.getGraphics();
		g.setPaint( new TexturePaint( backgroundTile, new Rectangle(0,0,backgroundTile.getWidth(),backgroundTile.getHeight())));
		g.fillRect(0,0,background.getWidth(),background.getHeight());
		backgroundY = backgroundTile.getHeight();

		hiloTiempo = new Thread(new Runnable() {public void run() {sumarTiempo();}});
		hiloTiempo.start();
	}

	/**
	 * Metodo que permite la dinamica para el tiempo.
	 */
	public void sumarTiempo()
	{
		if(!pause)
		{
			tiempo++;

			if(tiempo%5==0)
			{

			}
		}
		dormirTiempo();
	}

	/**
	 * Metodo que permite dormir el hilo.
	 */
	public void dormirTiempo()
	{
		try 
		{
			if(!gameEnded)
			{
				hiloTiempo.sleep(1000);
				sumarTiempo();
			}
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
	}

	/**
	 * Metodo para agregar un actor al arreglo de actores.
	 */
	public void addActor(Actor a) 
	{
		actors.add(a);
	}

	/**
	 * @return the agente
	 */
	public Agente getAgente() {
		return agente;
	}

	/**
	 * Metodo que permite actualizar que actores han cumplido su ciclo de vida
	 * y que nuevo actores hacen parte del escenario de juego.
	 */
	public void updateWorld()
	{

		int i = 0;
		while (i < actors.size()) {
			Actor m = (Actor)actors.get(i);
			if (m.isMarkedForRemoval()) {
				actors.remove(i);
			} else {
				m.act();
				i++;
			}
		}
	}

	/**
	 * Metodo que permite chequear las colisiones entre actores 
	 * e implementar el caso de colision para cada actor.
	 */
	public void checkCollisions() {

		for (int i = 0; i < actors.size(); i++) 
		{
			Actor a1 = (Actor)actors.get(i);
			Rectangle r1 = a1.getBounds();

			for (int j = i+1; j < actors.size(); j++) 
			{
				Actor a2 = (Actor)actors.get(j);
				if (a2!=null) {
					Rectangle r2 = a2.getBounds();

					if (r1.intersects(r2)) 
					{
						a1.collision(a2);
						a2.collision(a1);
					}
				}

			}
		}
	}

	/**
	 * Metodo que permite pintar la barra de sangre que posee el jugador.
	 * 
	 * @param g
	 */
	public void paintShields(Graphics2D g) {
		BufferedImage punt=spriteCache.getSprite("/menujuego/vida.png");
		g.drawImage( punt ,Escenario.WIDTH/2-260,Escenario.LIMITE-20,this);

		g.setPaint(Color.GRAY);
		g.fillRect(Escenario.WIDTH/2-180,Escenario.LIMITE,Escenario.MAX_SHIELDS,30);
		g.setPaint(Color.blue);
		g.fillRect((Escenario.WIDTH/2-180)+Escenario.MAX_SHIELDS-agente.getShields(),Escenario.LIMITE,agente.getShields(),30);
		g.setFont(new Font("Arial",Font.BOLD,20));

	}

	/**
	 * Metodo que permite pintar el fondo del menu.
	 * 
	 * @param g
	 */
	public void paintMenu(Graphics2D g) {
		BufferedImage punt=spriteCache.getSprite("/menujuego/fondomenu.png");
		g.drawImage( punt ,Escenario.WIDTH/2-400,Escenario.LIMITE-35,this);

	}

	/**
	 * Metodo que permite pintar la puntuacion durante el juego.
	 * 
	 * @param g
	 */
	public void paintScore(Graphics2D g) {
		BufferedImage punt=spriteCache.getSprite("/menujuego/puntaje.png");
		g.drawImage( punt ,Escenario.WIDTH/2-400,Escenario.LIMITE-20,this);
		g.setFont(new Font("Arial Rounded MT Bold",Font.BOLD,20));
		g.setPaint(Color.BLACK);
		g.drawString(agente.getScore()+"",Escenario.WIDTH/2-320,Escenario.LIMITE+25);

	}

	/**
	 * Metodo que permite pintar el indicador de armas especiales.
	 * @param g
	 */
	public void paintAmmo(Graphics2D g) {
		int xBase = 570+Escenario.MAX_SHIELDS+10;
		BufferedImage punt=spriteCache.getSprite("/menujuego/bomba.png");
		g.drawImage( punt ,Escenario.WIDTH/2+20,Escenario.LIMITE-20,this);
		for (int i = 0; i < agente.getClusterBombs();i++) {
			BufferedImage bomb = spriteCache.getSprite("bombUL.gif");
			g.drawImage( bomb ,xBase+i*bomb.getWidth(),Escenario.LIMITE+10,this);
		}
	}

	/**
	 * Metodo que permite pintar el indicador de invocaciones.
	 * @param g
	 */
	public void paintInv(Graphics2D g) {
		int xBase = 1000+num_inv;
		BufferedImage punt=spriteCache.getSprite("/menujuego/defensa.png");
		g.drawImage( punt ,Escenario.WIDTH/2+250,Escenario.LIMITE-20,this);
		for (int i = 0; i < agente.getClusterBombs();i++) {
			BufferedImage bomb = spriteCache.getSprite("/juego/inv02.png");
			g.drawImage( bomb ,xBase+(i*(bomb.getWidth()-10)),Escenario.LIMITE+5,this);
		}
	}

	/**
	 * Metodo que permite pintar el indicador de fotogramas por segundo (fps).
	 * @param g
	 */
	public void paintfps(Graphics2D g) {
		g.setFont( new Font("Arial",Font.BOLD,12));
		g.setColor(Color.white);
		if (usedTime > 0)
			g.drawString(String.valueOf(1000/usedTime)+" fps",Escenario.WIDTH-50,Escenario.LIMITE);
		else
			g.drawString("--- fps",Escenario.WIDTH-50,Escenario.LIMITE+10);
	}

	/**
	 * Metodo que permite pintar el indicador de tiempo durante el juego.
	 * @param g
	 */
	public void paintTime(Graphics2D g) 
	{
		BufferedImage punt=spriteCache.getSprite("/menujuego/tiempo.png");
		g.drawImage( punt ,Escenario.WIDTH/2+145,Escenario.LIMITE-20,this);

		g.setFont( new Font("Arial",Font.BOLD,12));
		g.setFont(new Font("Arial Rounded MT Bold",Font.BOLD,20));
		g.setColor(Color.BLACK);
		g.drawString(""+tiempo, Escenario.WIDTH/2+220,Escenario.LIMITE+25);	
	}

	/**
	 * Metodo que permite llamar a todos los metodos de pintar.
	 * 
	 * @param g
	 */
	public void paintStatus(Graphics2D g) {
		paintMenu(g);
		paintScore(g);
		paintShields(g);
		paintAmmo(g);
		paintfps(g);
		paintTime(g);
		paintInv(g);
	}

	/**
	 * Metodo que permite iniciar los graficos 2D para el escenario.
	 */
	public void paintWorld()
	{
		Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
		g.drawImage( background,
				0,0,Escenario.WIDTH,Escenario.HEIGHT,
				0,backgroundY,Escenario.WIDTH,backgroundY+Escenario.HEIGHT,this);


		for (int i = 0; i < actors.size(); i++) {
			Actor m = (Actor)actors.get(i);
			if (!(m instanceof Agente)) {
				m.paint(g);
			}

			if(peligro==true){
				if (m instanceof Agente) {
					m.paint(g);
				}
			}

		}
		paintStatus(g);
		strategy.show();
	}

	/**
	 * Metodo que permite pintar un titulo (Game Over) si el juego ha sido perdido.
	 */
	public void paintGameOver() {

		Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
		BufferedImage titulo = spriteCache.getSprite("/titulos/gameover.png");

		g.drawImage(titulo,Escenario.WIDTH/2-Escenario.PLAY_HEIGHT,Escenario.HEIGHT/2,this);
		strategy.show();

		guardarDatosJuego();

		ventanaPrincipal.getVentanaFin().setVisible(true);
		ventanaPrincipal.getVentanaFin().getEtiqPuntaje().setText(agente.getScore()+"");
		ventanaPrincipal.getVentanaFin().getEtiqTiempo().setText(tiempo+"");
	}

	/**
	 * Metodo que permite pintar un titulo (Game Won) si el juego ha sido ganado.
	 */
	public void paintGameWon() {

		Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
		BufferedImage titulo = spriteCache.getSprite("/titulos/gamewon.png");

		g.drawImage(titulo,Escenario.WIDTH/2-Escenario.PLAY_HEIGHT,Escenario.HEIGHT/2,this);
		strategy.show();

		guardarDatosJuego();


	}

	/**
	 * @return the spriteCache
	 */
	public SpriteCache getSpriteCache() {
		return spriteCache;
	}

	/**
	 * @param spriteCache the spriteCache to set
	 */
	public void setSpriteCache(SpriteCache spriteCache) {
		this.spriteCache = spriteCache;
	}

	/**
	 * @return the soundCache
	 */
	public SoundCache getSoundCache() {
		return soundCache;
	}

	/**
	 * @param soundCache the soundCache to set
	 */
	public void setSoundCache(SoundCache soundCache) {
		this.soundCache = soundCache;
	}

	@Override
	public void keyTyped(KeyEvent e) {


	}

	/**
	 * Metodo que permite enviar los eventos de teclado 
	 * 
	 * @param e Evento de teclado
	 */
	@Override
	public void keyPressed(KeyEvent e)
	{
		if(e.getKeyText(e.getKeyCode() ).equalsIgnoreCase("Escape"))
		{
			if(pause==false)
			{
				if(!gameEnded)
				{
					setPause(true);
				}
			}
			else
				setPause(false);
		}
		else if(peligro==true)
		{
			agente.keyPressed(e);

			for (int i = 0; i < actors.size(); i++)
			{
				((KeyListener) actors.get(i)).keyPressed(e);
			}
		}

	}

	/**
	 * Metodo que permite enviar los eventos de teclado al agente para que
	 * ejecute acciones
	 * @param e : Evento de teclado
	 */
	@Override
	public void keyReleased(KeyEvent e) {
		agente.keyReleased(e);
	}

	/**
	 * 
	 */
	public void game() 
	{
		ventana.setVisible(true);
		determinarNivel();

		usedTime=1000;
		initWorld();
		tiempo=0;

		while (isVisible() && !gameEnded) 
		{
			if(pause==false)
			{
				long startTime = System.currentTimeMillis();
				backgroundY--;
				if (backgroundY < 0)
					backgroundY = backgroundTile.getHeight();
				updateWorld();
				checkCollisions();
				paintWorld();
				usedTime = System.currentTimeMillis()-startTime;
				do {
					Thread.yield();
				} while (System.currentTimeMillis()-startTime< 17);
				reproducirRojos();
			}
			contarVirus();
		}

		if (titulo==1) {
			paintGameWon();
		}else{
			paintGameOver();
		}
	}

	/**
	 * Metodo que retorna el obejto administrador.
	 * 
	 * @see co.sumset.objetos.Escenario#getAdministrador()
	 */
	@Override
	public Administrador getAdministrador() 
	{
		return this;
	}

	/**
	 * @return the bodegaImagenes
	 */
	public BodegaImagenes getBodegaImagenes() {
		return bodegaImagenes;
	}

	/**
	 * @param pause the pause to set
	 */
	public void setPause(boolean pause) {
		this.pause = pause;
	}

	/**
	 * @return the peligro
	 */
	public boolean isPeligro() {
		return peligro;
	}

	/**
	 * @param peligro the peligro to set
	 */
	public void setPeligro(boolean peligro) {
		this.peligro = peligro;
	}

	/**
	 * Metodo que determina el nivel de dificultad y asi mismo el valor
	 *  de las variables para generar un Escenario de juego.
	 */
	public void determinarNivel(){

		if (dificultad==1) {
			nivel = new Nivel(this, 1, 2, 3, 6, 2, 1, 0.01,0);
			num_inv=1;
			setDificultad(1);
		}

		if (dificultad==2) {
			nivel = new Nivel(this, 3, 2, 3, 8, 3, 2, 0.007,1);
			num_inv=2;
			setDificultad(2);
		}

		if (dificultad==3) {
			nivel = new Nivel(this, 5, 3, 4, 8, 5, 3, 0.006,2);
			num_inv=3;
			setDificultad(3);
		}

		if (dificultad==4) {
			nivel = new Nivel(this, 8, 3, 6, 7, 7, 3, 0.006,3);
			num_inv=3;
			setDificultad(4);
		}	
	}

	/**
	 * Metodo que permite guardar los dato, una vez que el juego 
	 * ha sido finalizado; sea un juego ganado o un juego perdido.
	 * 
	 */
	private void guardarDatosJuego() 
	{
		try 
		{
			Date fecha = new Date();
			String correo_usu=ventanaPrincipal.getUsuario().getCorreo();
			puntaje= getAgente().getShields();

			int dia=fecha.getDay();
			int mes=fecha.getMonth();
			int anio=fecha.getYear();

			Partida nuevo = new Partida(agente.getScore()+"",anio,mes,dia,correo_usu,tiempo+"",dificultad+"");

			ventanaPrincipal.getVentanaFin().setVisible(true);
			ventanaPrincipal.getVentanaFin().getEtiqPuntaje().setText(agente.getScore()+"");
			ventanaPrincipal.getVentanaFin().getEtiqTiempo().setText(tiempo+"");
			switch (dificultad)
			{
			case 1:nuevo.setNivel("Facil");break;
			case 2:nuevo.setNivel("Medio");break;
			case 3:nuevo.setNivel("Dificil");break;
			case 4:nuevo.setNivel("Dificil");break;
			default:break;
			}


			ventanaPrincipal.getConexionPartida().crearPartida(nuevo);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	/**
	 * @return the nivel
	 */
	public Nivel getNivel() {
		return nivel;
	}

	/**
	 * @return the dificultad
	 */
	public int getDificultad() {
		return dificultad;
	}

	/**
	 * @param dificultad the dificultad to set
	 */
	public void setDificultad(int dificultad) {
		this.dificultad = dificultad;
	}

	
	/**
	 * @return the ventana
	 */
	public JFrame getVentana() {
		return ventana;
	}

	/**
	 * @param ventana the ventana to set
	 */
	public void setVentana(JFrame ventana) {
		this.ventana = ventana;
	}

	
	/**
	 * @return the panel
	 */
	public JPanel getPanel() {
		return panel;
	}

	/**
	 * @param panel the panel to set
	 */
	public void setPanel(JPanel panel) {
		this.panel = panel;
	}

	/**
	 * @return the strategy
	 */
	public BufferStrategy getStrategy() {
		return strategy;
	}

	/**
	 * @param strategy the strategy to set
	 */
	public void setStrategy(BufferStrategy strategy) {
		this.strategy = strategy;
	}
}
